package com.nineshadow.games.api;

import cn.stylefeng.guns.modular.system.controller.BixingInterfaceController;
import cn.stylefeng.guns.modular.system.model.GameLog;
import cn.stylefeng.guns.modular.system.model.GameUser;
import cn.stylefeng.guns.modular.system.model.RewardLog;
import cn.stylefeng.guns.modular.system.model.Room;
import cn.stylefeng.guns.modular.system.service.*;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.nineshadow.games.Enum.GameStatusEnum;
import com.nineshadow.games.Enum.GameTypeEnum;
import com.nineshadow.games.Enum.ResultStatusEnum;
import com.nineshadow.games.constants.CommonConstants;
import com.nineshadow.games.core.GameRoom;
import com.nineshadow.games.entity.CommonGameUser;
import com.nineshadow.games.interfaces.IGameUser;
import com.nineshadow.games.launch.GameWorld;
import com.nineshadow.games.launch.HallServer;
import com.nineshadow.games.redismq.PublishService;
import com.nineshadow.games.util.MyUtils;
import com.nineshadow.games.entity.PollHandler;
import com.nineshadow.games.util.RedisLock;
import com.nineshadow.games.util.RedisUtil;
import com.nineshadow.games.vo.UserTagsVo;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service("game")
public class GameApi {
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private PublishService publishService;
    @Autowired
    private IGameUserService gameUserService;
    @Autowired
    private IGameTagLogService gameTagLogService;
    @Autowired
    private IGameLogService gameLogService;

    @Autowired
    private IGameTagService gameTagService;
    @Autowired
    private IRoomService roomService;
    @Autowired
    private IRewardLogService rewardLogService;

    @Autowired
    private IScoreLevelService scoreLevelService;
    @Autowired
    private RedisLock redisLock;

    /**
     * 获取对手标签信息
     *
     * @param ctx
     * @return
     */
    public Object getTags(final ChannelHandlerContext ctx) {
        Map<String, Object> map = new HashMap<>();
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            GameLog target = gameLogService.getOpponentInfo(userId);
            List<UserTagsVo> userTagsVos = gameTagService.getUserTags(target.getUserId(),target.getGameType());
            map.put("tags", userTagsVos);
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 评价
     *
     * @param ctx
     * @param tagId
     * @return
     */
    public synchronized Object like(final ChannelHandlerContext ctx, int tagId) {
        Map<String, Object> map = new HashMap<>();
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            GameLog target = gameLogService.getOpponentInfo(userId);
            boolean flag = gameTagService.isLikeTagByRoom(target.getRoomId(), userId, tagId);
            if (flag) {
                throw new RuntimeException("已经评价过了");
            }
            gameTagLogService.saveGameTagLog(target.getRoomId(), userId,
                    target.getUserId(), tagId,target.getGameType());
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 登录
     *
     * @param ctx
     * @param userId
     * @return
     */
    public Object login(final ChannelHandlerContext ctx, int userId) {
        Map<String, Object> map = new HashMap<>();
        GameUser gameUser = gameUserService.selectById(userId);
        Assert.notNull(gameUser, "用户不存在");
        IGameUser userVo = GameWorld.getOnlineUser(userId);
        if (Objects.isNull(userVo)) {
            userVo = new CommonGameUser();
        }
        userVo.setCtx(ctx);
        userVo.setUserInfo(gameUser);
        //存储登陆信息
        ctx.channel().attr(GameWorld.userId_key).set(userId);
        GameWorld.putOnlineUser(userId, userVo);
        GameWorld.getSessionMap().put(ctx.channel().id().asLongText(), ctx);
        return ResultStatusEnum.putSuccessMsg(map);
    }

    /**
     * 退出登录
     */
    public Map<String, Object> quit(final ChannelHandlerContext ctx) {
        GameWorld.getSessionMap().remove(ctx.channel().id().asLongText());
        Integer id = ctx.channel().attr(GameWorld.userId_key).get();
        log.info("退出登陆:{}",id);
        IGameUser userVo = GameWorld.getOnlineUser(id);
        userVo.offline(ctx);
        // 延迟断开session，不然前端收不到返回信息
        GameWorld.SCHEDULED_EXECUTOR_SERVICE.schedule((Runnable) ctx::close, 1, TimeUnit.SECONDS);
        log.info("账号 " + id + " 退出登陆");
        return ResultStatusEnum.putSuccessMsg(new HashMap<>(), "退出登录成功!");
    }

    /**
     * 开始游戏
     *
     * @param ctx
     * @return
     */
    public Object startGame(final ChannelHandlerContext ctx) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            log.info("userId:{}---startGame",userId);
            IGameUser userVo = GameWorld.getOnlineUser(userId);
            GameLog gameLog = gameLogService.selectOne(new EntityWrapper<GameLog>()
                    .eq("user_id", userId)
                    .eq("status", GameStatusEnum.MATCH.getCdoe())
                    .orderBy("create_time",false).last("limit 1"));
            Assert.notNull(gameLog, "没有匹配记录，请重新匹配");
            String roomId=String.valueOf(gameLog.getRoomId());
            if (redisLock.lock(roomId)) {
                //房间玩家
                redisUtil.sSet(CommonConstants.ROOM+roomId,userId);
                //房间是否已存在
                boolean roomExist=redisUtil.sIsmember(CommonConstants.AllROOM,roomId);
                Set<Object> roomMember=redisUtil.getSetMembers(CommonConstants.ROOM+roomId);
                subscriber(userId, userVo, roomId);
                log.info("roomUsers:{}------roomExist:{}",roomMember.size(),roomExist);
                if (roomMember.size() >= 2&&!roomExist) {
                    //加入redis
                    redisUtil.sSet(CommonConstants.AllROOM,roomId);
                    log.info("两人都加入房间开始游戏流程：{}",roomId);
                    GameRoom gameRoom=new GameRoom(GameTypeEnum.getByType(gameLog.getGameType())
                            ,roomId);
                    GameWorld.putRoom(roomId,gameRoom);
                } else {
                    //等待10s，对方还未进入游戏则推送超时
                    GameWorld.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
                        boolean ismember=redisUtil.sIsmember(CommonConstants.AllROOM,roomId);
                        Room room=roomService.selectById(roomId);
                        if (!ismember&&Objects.nonNull(room)&&room.getStatus()!=0) {
                            log.info("游戏开始超时处理：{}", roomId);
                            publishService.publish(roomId,userVo.getUserId(), MyUtils.putMsg("timeOut"));
                            gameLogService.updateRoomState(gameLog.getRoomId(),GameStatusEnum.GAME_OVER);
                            //移除redis中的房间
                            redisUtil.removeRoom(roomId);
                        }
                    }, 10, TimeUnit.SECONDS);
                }
                redisLock.unlock(roomId);
            }
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 初始用户信息  订阅消息
     *
     * @param userId
     * @param userVo
     * @param roomId
     */
    private void subscriber(Integer userId, IGameUser userVo, String roomId) {
        log.info("初始用户信息 userId:{}---roomId:{}",userId,roomId);
        //用户信息
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put(CommonConstants.USER_SCORE, 0);
        userInfo.put(CommonConstants.USER_ROOM, roomId);
        redisUtil.hmset(CommonConstants.USER + userId, userInfo);
        userVo.setLastRoomId(roomId);
        if(Objects.nonNull(userVo.getPollHandler())){
            userVo.getPollHandler().setDone(true);
        }
        PollHandler pollHandler =new PollHandler(userId);
        userVo.setPollHandler(pollHandler);
    }

    /**
     * 对手信息
     *
     * @param ctx
     * @return
     */
    public Object opponentInfo(final ChannelHandlerContext ctx) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            log.info("获取对手信息：{}",userId);
            IGameUser userVo = GameWorld.getOnlineUser(userId);
            Assert.notNull(userVo,"请重新开始游戏");
            GameLog gameLog = gameLogService.getOpponentInfo(userId);
            GameUser targetUser = gameUserService.selectById(gameLog.getUserId());
            log.info("玩家openId:{}--对手openId:{}",userVo.getUserInfo().getOpenId(),targetUser.getOpenId());
            boolean isRelation = BixingInterfaceController.watchFriend(userVo.getUserInfo().getOpenId(),
                    targetUser.getOpenId(), true,GameTypeEnum.getByType(gameLog.getGameType()));
            map.put("userId", targetUser.getId());
            map.put("openId", targetUser.getOpenId());
            map.put("nickName", targetUser.getNickName());
            map.put("avatar", targetUser.getAvatar());
            map.put("gender", targetUser.getGender());
            map.put("relation", isRelation ? 0 : 1);
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 退出房间，取消订阅
     *
     * @param ctx
     * @return
     */
    public Object quitRoom(final ChannelHandlerContext ctx) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            IGameUser userVo = GameWorld.getOnlineUser(userId);
            if(redisLock.lock(CommonConstants.QUIT_ROOM+roomId)) {
                gameLogService.quit(userId);
                redisLock.unlock(CommonConstants.QUIT_ROOM+roomId);
            }
            userVo.removeUserInfo();
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 更新分数
     *
     * @param ctx
     * @param score
     * @param type
     * @return
     */
    public Object updateScore(final ChannelHandlerContext ctx,
                              int score, int type) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            boolean roomExist=HallServer.redisUtil.sIsmember(CommonConstants.AllROOM,roomId);
            if(roomExist){
                Integer targetId = getOpponentId(roomId,userId);
                redisUtil.hset(CommonConstants.USER + userId, CommonConstants.USER_SCORE, score);
                if(Objects.nonNull(targetId)){
                    Map<String, Object> sendMsg = new HashMap<>();
                    sendMsg.put("userId", userId);
                    sendMsg.put("score", score);
                    publishService.publish(roomId,targetId, MyUtils.putMsg("gameScore", sendMsg));
                }
            }
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 获取用户所在房间
     *
     * @param userId
     * @return
     * @throws Exception
     */
    public static String getUserInRoom(Integer userId) throws Exception {
        Object roomId = HallServer.redisUtil.hget(CommonConstants.USER + userId, CommonConstants.USER_ROOM);
        Assert.notNull(roomId, "你已退出或网络断开，请重新匹配");
        return roomId.toString();
    }

    /**
     * 对手id
     * @param roomId
     * @param userId
     * @return
     */
    public static Integer getOpponentId(String roomId,Integer userId){
        Set<Object> roomMember=HallServer.redisUtil.getSetMembers(CommonConstants.ROOM+roomId);
        for (Object user: roomMember) {
            if(Objects.nonNull(user)&&!user.equals(userId)){
                return Integer.parseInt(String.valueOf(user));
            }
        }
        return null;
    }

    /**
     * 邀请再来一局
     *
     * @param ctx
     * @return
     */
    public Object again(final ChannelHandlerContext ctx) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            if(redisLock.lock(CommonConstants.AGAIN+roomId)){
                Object agenNum=redisUtil.hget(CommonConstants.AGAIN+roomId,CommonConstants.AGAIN_NUM);
                if(Objects.nonNull(agenNum)&&Integer.parseInt(agenNum.toString())>0){
                    log.info("对方已发邀请");
                    redisLock.unlock(CommonConstants.AGAIN+roomId);
                    return ResultStatusEnum.putFailureMsg(map, "对方已发邀请");
                }
                GameLog gameLog = gameLogService.getOpponentInfo(userId);
                redisUtil.hincr(CommonConstants.AGAIN+roomId,CommonConstants.AGAIN_NUM,1);
                Map<String, Object> sendMsg = new HashMap<>();
                sendMsg.put("userId", userId);
                publishService.publish(roomId,gameLog.getUserId(), MyUtils.putMsg("again", sendMsg));
                redisLock.unlock(CommonConstants.AGAIN+roomId);
            }
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 同意再来一局
     *
     * @param ctx
     * @param gameType 游戏类型
     * @param inviteId 发起人
     * @return
     */
    public Object agree(final ChannelHandlerContext ctx, int gameType, int inviteId) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            List<Integer> users = new ArrayList<>();
            users.add(userId);
            users.add(inviteId);
            List<GameUser> gameUsers = gameUserService.selectList(new EntityWrapper<GameUser>()
                    .in("id", users));
            roomService.createRoom(GameTypeEnum.getByType(gameType), gameUsers);
            publishService.publish(roomId,inviteId, MyUtils.putMsg("agree"));
            publishService.publish(roomId,userId, MyUtils.putMsg("agree"));
            redisUtil.del(CommonConstants.AGAIN+roomId);
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 打赏记录
     *
     * @param ctx
     * @param name
     * @return
     */
    public Object reward(final ChannelHandlerContext ctx, String name) {
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            GameLog gameLog = gameLogService.getOpponentInfo(userId);
            RewardLog rewardLog = new RewardLog();
            rewardLog.setAwardName(name);
            rewardLog.setCreateTime(new Date());
            rewardLog.setFromId(userId);
            rewardLog.setToId(gameLog.getUserId());
            rewardLog.setGameType(gameLog.getGameType());
            rewardLogService.insert(rewardLog);
            Map<String,Object> reward=new HashMap<>();
            reward.put("userId",gameLog.getUserId());
            reward.put("name",name);
            publishService.publish(roomId,gameLog.getUserId(),MyUtils.putMsg("reward",reward));
            return ResultStatusEnum.putSuccessMsg(map);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 开启/关闭语音
     * @param ctx
     * @param type
     * @return
     */
    public Object openVoice(final ChannelHandlerContext ctx,int type){
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            GameLog gameLog = gameLogService.getOpponentInfo(userId);
            Map<String,Object> openVoiceState=new HashMap<>();
            openVoiceState.put("userId",userId);
            openVoiceState.put("type",type);
            publishService.publish(roomId,gameLog.getUserId(),MyUtils.putMsg("openVoice",openVoiceState));
            return ResultStatusEnum.putSuccessMsg(map);
        }catch (Exception e){
            log.error(e.getMessage(),e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * 发送表情
     * @param ctx
     * @param type
     * @return
     */
    public Object emoji(final ChannelHandlerContext ctx,int type){
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            String roomId = getUserInRoom(userId);
            GameLog gameLog = gameLogService.getOpponentInfo(userId);
            Map<String,Object> sendEmoji=new HashMap<>();
            sendEmoji.put("type",type);
            sendEmoji.put("userId",userId);
            publishService.publish(roomId,gameLog.getUserId(),MyUtils.putMsg("emoji",sendEmoji));
            return ResultStatusEnum.putSuccessMsg(map);
        }catch (Exception e){
            log.error(e.getMessage(),e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }

    /**
     * @Author ly
     * @Description 获取结算信息
     * @Date 12:10 2020/1/15
     * @Param [ctx]
     * @return java.lang.Object
     **/
    public Object getResult(final ChannelHandlerContext ctx){
        Map<String, Object> map = new HashMap<>(2);
        try {
            Integer userId = ctx.channel().attr(GameWorld.userId_key).get();
            log.info("获取结算信息：{}",userId);
            GameLog gameLog=gameLogService.selectOne(new EntityWrapper<GameLog>()
                    .eq("user_id",userId)
                    .eq("status",GameStatusEnum.GAME_OVER.getCdoe())
                    .orderBy("room_id",false)
                    .last("limit 1"));
            Assert.notNull(gameLog,"没有游戏记录");
            Map<String,Object> result=new HashMap<>();
            Map<String, Object> level = scoreLevelService.getUserLevel(userId, gameLog.getGameType());
            result.put("userId", userId);
            result.put("level", level.get("level"));
            result.put("ratio", level.get("ratio"));
            result.put("getIntegral", gameLog.getChangeScore());
            result.put("coins", gameLog.getChangeCoin());
            result.put("win", gameLog.getIsWin());
            result.put("roomId", gameLog.getRoomId());
            log.info("userId:{}----roomId:{}-----result:{}",userId,gameLog.getRoomId(),result);
            return ResultStatusEnum.putSuccessMsg(result);
        }catch (Exception e){
            log.error(e.getMessage(),e);
            return ResultStatusEnum.putFailureMsg(map, e.getMessage());
        }
    }
}
